#ifdef MAODV

/*****************************************************************************
 *
 * Copyright (C) 2003 CSHCN, University of Maryland.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Dionysus Blazakis <dblaze@umd.edu>
 *          Jonathan Polley <jpolley@umd.edu>
 *          Dan Rusk <drusk@umd.edu>
 *          Manish Karir <karir@umd.edu>
 *          Jonathan McGee <mcgee@umd.edu>
 *
 *****************************************************************************/

#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/igmp.h>

#include "multicast_routing_table.h"

#include "igmp.h"
#include "aodv_timeout.h"
#include "packet_queue.h"
#include "aodv_rerr.h"
#include "aodv_hello.h"
#include "aodv_socket.h"
#include "k_route.h"
#include "k_mroute.h"
#include "timer_queue.h"
#include "defs.h"
#include "debug.h"
#include "params.h"
#include "seek_list.h"
#include "maodv_mact.h"
#include "maodv_grph.h"

static struct timer mrt_debug_timer;

struct multicast_routing_table mrt_tbl;

inline static unsigned int hashing(u_int32_t addr);

/*
   Local static functions
*/

/*
   hashing( addr )
   A VERY simple "hash" function.  Taskes the multicast address as 
   a key and hacks off the most significant bits. (how many bits it 
   masks off depends on the size of the hash table. look at the
   MRT_TABLEMASK define) This becomes an index into the hash table.

*/
inline static unsigned int hashing(u_int32_t addr)
{
    return( addr & MRT_TABLEMASK );
}

/*
 *  Public functions
 */

/*
 *  mrt_table_init( )
 *  Create an empty table with 0 entries.
 */
void mrt_table_init()
{
    int index;
    int fd;

    mrt_tbl.num_entries = 0;

    /* init buckets with null pointers. */
    for( index = 0; index < MRT_TABLESIZE; index++ ) {
        mrt_tbl.tbl[index] = NULL;
    }

    /* CHECK: add in with other log code?  not yet... */

    /* every 10 seconds? */
    mrt_debug_timer.handler = (timeout_func_t) &mrt_table_dump;
    mrt_debug_timer.data = NULL;

    fd = open( "nh_log", O_RDWR | O_CREAT | O_TRUNC, S_IROTH |
        S_IWUSR | S_IRUSR | S_IRGRP );

    close ( fd );
    
    fd = open( "mrt_log", O_RDWR | O_CREAT | O_TRUNC, S_IROTH |
        S_IWUSR | S_IRUSR | S_IRGRP );

    close ( fd );

    mrt_table_dump();
}

/*
 *  mrt_table_destroy( )
 *  Destroy table and each entry.
 */
void mrt_table_destroy()
{
    int index;
    mrt_table_t *curr;
    mrt_table_t *dead;

    for( index = 0; index < MRT_TABLESIZE; index++ ) {
        curr = mrt_tbl.tbl[index];

        while( curr ) {
            dead = curr;
            curr = curr->next;

            /*   TODO: Timeout multicast routes.
            timer_remove( &dead->mrt_timer ); */

            mrt_nexthop_remove_all( dead );

            mrt_sources_remove_all( dead );
            free( dead );
        }

        /* safety */
        mrt_tbl.tbl[index] = NULL;
    }

    mrt_tbl.num_entries = 0;
}

/*
 *  mrt_table_insert( route info )
 *  This creates a route with NO next hop info.  Any next hop info must be 
 *  added with other function calls.
 */
mrt_table_t *mrt_table_insert(u_int32_t grp_addr, u_int32_t grp_leader_addr,
    u_int32_t grp_seqno,
    unsigned int ifindex,
    u_int8_t leader_hcnt,
    u_int8_t is_member)
{
    mrt_table_t *new_entry;
    int index;

    /* if it's already in the table, we can't add it */
    if ( mrt_table_find( grp_addr ) ) {
        return NULL;
    }

    index = hashing( grp_addr );

    new_entry = (mrt_table_t *) malloc( sizeof( mrt_table_t ) );
    if ( !new_entry ) {
        /* go down if theres no memory?  probably best idea */
        fprintf( stderr, "multicast_routing_table.c: Cannot allocate memory for route.\n" );
        exit( -1 );
    }

    new_entry->grp_addr = grp_addr;
    new_entry->grp_leader_addr = grp_leader_addr;
    new_entry->grp_seqno = grp_seqno;
    new_entry->am_leader = 0;   /* false... i am not leader! */

    new_entry->ifindex = ifindex;
    new_entry->leader_hcnt = leader_hcnt;
    new_entry->is_member = is_member;
    new_entry->is_router = 0;    /* false */
    new_entry->is_broken = 0;    /* false */
    new_entry->in_repair = 0;    /* false */
    new_entry->is_gateway = 0;   /* false */

    /* initialize timers */

    /*  TODO: Timeout routes.
    new_entry->mrt_timer.handler = &multicast_route_expire_timeout;
    new_entry->mrt_timer.data = new_entry; */

    /* empty next hop list */
    new_entry->nexthops = NULL;
    new_entry->activated_upstream_cnt = 0;
    new_entry->activated_downstream_cnt = 0;

    new_entry->sources = NULL;
    new_entry->origlist = NULL;

    /* make me first, so next will be whoever is currently top of bucket */
    new_entry->next = mrt_tbl.tbl[index];
    mrt_tbl.tbl[index] = new_entry;
    mrt_tbl.num_entries++;
    /* CHECK: add kernel route entry */
    
    /* grph timer */
    new_entry->grph_timer.handler=(timeout_func_t) &grph_send_callback;
    new_entry->grph_timer.data=new_entry;

    /* start new timer */

    /*  TODO: Timeout routes.
    DEBUG(LOG_INFO, 0, "New timer for %s, life=%d",
          ip_to_str(new_entry->grp_addr), ACTIVE_ROUTE_TIMEOUT );

    timer_set_timeout( &new_entry->mrt_timer, ACTIVE_ROUTE_TIMEOUT );
    */

    return new_entry;
}

/*
 *  mrt_table_update_timeout( route, lifetime )
 *  Resets the route timeout.  Renews the lease in the route table.
 */
NS_INLINE mrt_table_t *mrt_table_update_timeout(mrt_table_t * mrt,
    u_int32_t lifetime)
{
    struct timeval new_timeout;

    /* make sure route tp update is somewhat legal. */
    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to update NULL route.");

        return NULL;
    }

    gettimeofday( &new_timeout, NULL );
    timeval_add_msec( &new_timeout, lifetime );

    /* TODO: Timeout routes.
    if ( timeval_diff( &mrt->mrt_timer.timeout, &new_timeout ) < 0 ) {
        timer_set_timeout( &mrt->mrt_timer, lifetime );
    }
    */

    return mrt;
}

/*
 *  mrt_table_find( group address )
 *  This looks up an entry in the routing table.  If none is found,
 *  NULL is returned.  Otherwise a pointer to the entry is returned.
 */
mrt_table_t *mrt_table_find(u_int32_t grp_addr)
{
    mrt_table_t *curr;
    int index;

    index = hashing( grp_addr );
    curr = mrt_tbl.tbl[index];

    /* If nothing is in the bucket the group address hashes to,
       return NULL.  It's definitely not here. */
    if ( !curr ) {
        return NULL;
    }

    while( curr ) {
        if ( curr->grp_addr == grp_addr ) {
            break;
        }

        curr = curr->next;
    }

    return curr;
}

/*
 *  mrt_table_delete( group addr )
 *  Delete a route from the routing table.
 */
void mrt_table_delete(u_int32_t grp_addr)
{
    int index;
    mrt_table_t * curr, * prev;

    index = hashing( grp_addr );

    curr = mrt_tbl.tbl[index];
    prev = NULL;

    if ( !curr ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to delete an unknown route.");
        return;
    }

    while( curr ) {
        if ( curr->grp_addr == grp_addr ) {
            break;
        }

        prev = curr;
        curr = curr->next;
    }

    if ( !curr ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to delete an unknown route.");
        return;
    }

    /* CHECK: Remove entry from kernel routing table. */
    /*  TODO: Timeout routes.
    timer_remove( &curr->mrt_timer ); */

    mrt_nexthop_remove_all( curr );

    if ( !prev ) {
        /* Deleting the head */
        mrt_tbl.tbl[index] = curr->next;
    } else {
        prev->next = curr->next;
    }

    mrt_tbl.num_entries--;

    free( curr );
}

/*
 *  mrt_nexthop_add( multi route, nh addr, direction )
 *  Adds a nexthop entry into a routes information. Starts out unactivated.
 */
mrt_nexthop_t * mrt_nexthop_add(mrt_table_t * mrt, u_int32_t addr, 
    u_int8_t direction, u_int32_t grp_seqno)
{
    mrt_nexthop_t *newhop;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to add a next hop to an unknown route.");
        return NULL;
    }

    newhop = mrt_nexthop_find( mrt, addr );
    if ( newhop ) {    /* if it's already there, update it? */
        return newhop;
    }

    newhop = (mrt_nexthop_t *) malloc( sizeof( mrt_nexthop_t ) );
    if ( !newhop ) {
        fprintf( stderr, "multicast_routing_table.c: Error allocating nexthop entry.\n" );
        exit( -1 );
    }

    newhop->addr = addr;
    newhop->direction = direction;
    newhop->activated = 0;    /* 0 == FALSE */
    newhop->grp_hcnt = 0xff;  /* start with infinite hcnt */
    newhop->grp_seqno=grp_seqno;

    newhop->next = mrt->nexthops;
    mrt->nexthops = newhop;

    return newhop;
}

/*
 *  mrt_nexthop_remove( route, addr )
 *  Remove a nexthop entry from a kernel route info.
 */
void mrt_nexthop_remove(mrt_table_t * mrt, u_int32_t addr )
{
    mrt_nexthop_t * curr, * prev;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to remove a next hop from an unknown route.");
        return;
    }

    curr = mrt->nexthops;
    prev = NULL;

    if ( !curr ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to remove a next hop [%s] that doesn't exist.",
             ip_to_str( addr ) );
        return;
    }

    while( curr ) {
        if ( curr->addr == addr ) {
            break;
        }

        prev = curr;
        curr = curr->next;
    }

    if ( !curr ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to remove a next hop that doesn't exist.");
        return;
    }

    mrt_nexthop_deactivate(mrt, addr);

    if ( !prev ) {
        mrt->nexthops = curr->next;
    } else {
        prev->next = curr->next;
    }

    free( curr );
}

/*
 *  mrt_nexthop_activated( route, addr, on/off )
 *  Sets a certain nexthop flag indicating if this route is active or not.
 */
void mrt_nexthop_activate(mrt_table_t * mrt, u_int32_t addr)
{
    FILE *nh_log;
    mrt_nexthop_t *curr;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to activate a next hop for a NULL route.");
        return;
    }

    curr = mrt->nexthops;

    while( curr ) {
        if ( curr->addr == addr ) {
            break;
        }

        curr = curr->next;
    }

    if ( !curr ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to activate a next hop that doesn't exist.");
        return;
    }

    curr->activated = 1;

    if ( curr->direction == MRT_UPSTREAM ) {
        mrt->activated_upstream_cnt++;
    } else if ( curr->direction == MRT_DOWNSTREAM ) {
        mrt->activated_downstream_cnt++;
    }
    
    nh_log=fopen("nh_log", "a");
    fprintf(nh_log, "ACTIVATE: %x %x\n", mrt->grp_addr, addr);
    fclose(nh_log);
}

/*
 *  mrt_nexthop_deactivate( route, addr )
 *  Sets a certain nexthop flag indicating if this route is active or not.
 */
void mrt_nexthop_deactivate(mrt_table_t * mrt, u_int32_t addr)
{
    FILE *nh_log;
    mrt_nexthop_t *curr;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to activate a next hop for a NULL route.");
        return;
    }

    curr = mrt->nexthops;

    while( curr ) {
        if ( curr->addr == addr ) {
            break;
        }

        curr = curr->next;
    }

    if ( !curr ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to deactivate a next hop that doesn't exist.");
        return;
    }

    if(curr->activated == 1){
        if ( curr->direction == MRT_UPSTREAM ) {
            mrt->activated_upstream_cnt--;
        } else if ( curr->direction == MRT_DOWNSTREAM ) {
            mrt->activated_downstream_cnt--;
        }

        curr->activated=0;
        
        nh_log=fopen("nh_log", "a");
        fprintf(nh_log, "DEACTIVATE: %x %x\n", mrt->grp_addr, addr);
        fclose(nh_log);
    }
}

/*
 *  mrt_nexthop_get_upstream( table entry )
 *  Returns pointer to activated upstream next hop.
 */
mrt_nexthop_t *mrt_table_get_activated_upstream(mrt_table_t * mrt)
{
    mrt_nexthop_t *curr;
        
    if ( !mrt ) { 
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to get an upstream for a NULL route.");
        return NULL;
    }
        
    curr = mrt->nexthops;

    while( curr ) {
        if ( ( curr->activated ) && ( curr->direction == MRT_UPSTREAM ) ) {
            break;
        }
    
        curr = curr->next;
    }
        
    return curr;
}

/*
 *  mrt_nexthop_remove_all( table entry )
 *  Destroys every entry in next_hop list for a multicast route.
 */
void mrt_nexthop_remove_all(mrt_table_t * mrt)
{
    mrt_nexthop_t *curr, *dead;

    curr = mrt->nexthops;

    while( curr ) {
        dead = curr;
        curr = curr->next;

        free( dead );
    }

    /* easier to debug if it's NULL instead of garbage */
    mrt->nexthops = NULL;
    mrt->activated_upstream_cnt = 0;
    mrt->activated_downstream_cnt = 0;
}

/*
 *  mrt_sources_find( table entry, source )
 *  Finds an entry in source list for a multicast route.
 */
mrt_src_t *mrt_sources_find(mrt_table_t * mrt, u_int32_t src)
{
    mrt_src_t *curr;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to find a source to an unknown route.");
        return NULL;
    }

    curr = mrt->sources;

    while( curr ) {
        if ( curr->src_addr == src ) {
            break;
        }

        curr = curr->next;
    }

    return curr;
}

/*
 *  mrt_sources_add( table entry, source )
 *  Adds an entry in source list for a multicast route.
 */
void mrt_sources_add(u_int32_t src, u_int32_t dest)
{
    mrt_table_t *mrt;
    mrt_src_t *newsrc;

    mrt = mrt_table_find( dest );
    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to add a source to an unknown route.");
        return;
    }

    newsrc = mrt_sources_find( mrt, src );
    if ( newsrc ) {    /* if it's already there, update it? */
        return;
    }

    newsrc = (mrt_src_t *) malloc( sizeof( mrt_src_t ) );
    if ( !newsrc ) {
        /* if no memory... quit */
        fprintf( stderr, "multicast_routing_table.c: Error allocating source entry.\n" );
        exit( -1 );
    }

    kernel_mrt_add_route( src, dest, 0 );

    newsrc->src_addr = src;
    newsrc->next = mrt->sources;
    mrt->sources = newsrc;
}

/*
 *  mrt_sources_remove_all( table entry )
 *  Destroys every entry in source list for a multicast route.
 */
void mrt_sources_remove_all(mrt_table_t * mrt)
{
    mrt_src_t *curr, *dead;

    curr = mrt->sources;

    while( curr ) {
        dead = curr;
        curr = curr->next;

        kernel_mrt_del_route( dead->src_addr, mrt->grp_addr, 0 );

        free( dead );
    }

    /* easier to debug if it's NULL instead of garbage */
    mrt->sources = NULL;
}

/*
 *  mrt_origlist_find( table entry, orig )
 *  Finds an entry in originator list for a multicast route.
 */
mrt_orig_t *mrt_origlist_find(mrt_table_t * mrt, u_int32_t orig)
{
    mrt_orig_t *curr;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to find a source to an unknown route.");
        return NULL;
    }

    curr = mrt->origlist;

    while( curr ) {
        if ( curr->orig_addr == orig ) {
            break;
        }

        curr = curr->next;
    }

    return curr;
}

/*
 *  mrt_origlist_add( table entry, orig, seqno, hcnt )
 *  Adds an entry in originator list for a multicast route.
 */
mrt_orig_t * mrt_origlist_add(mrt_table_t *mrt, u_int32_t orig )
{
    mrt_orig_t *neworig;

    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to add a source to an unknown route.");
        return NULL;
    }

    neworig = mrt_origlist_find( mrt, orig );
    if ( neworig ) {    /* if it's already there, update it? */
        return neworig;
    }

    neworig = (mrt_orig_t *) malloc( sizeof( mrt_orig_t ) );
    if ( !neworig ) {
        fprintf( stderr, "multicast_routing_table.c: Error allocating orig entry.\n" );
        exit( -1 );
    }

    neworig->orig_addr = orig;
    neworig->grp_seqno = 0;
    neworig->grp_hcnt = 0xff;
    neworig->next = mrt->origlist;
    neworig->up = mrt;
    mrt->origlist = neworig;

    /* do timeout stuff */
    neworig->cleanup_timer.handler = (timeout_func_t) &mrt_origlist_remove;
    neworig->cleanup_timer.data = neworig;
    timer_set_timeout( &( neworig->cleanup_timer ), RREP_WAIT_TIME );

    return neworig;
}

/*
 *  mrt_origlist_remove( orig entry )
 *  For the timeout callback.  Removes a single entry in the list.
 */
void mrt_origlist_remove(mrt_orig_t *entry)
{
    mrt_orig_t * curr = NULL, * prev = NULL;

    DEBUG( LOG_DEBUG, 0, "In origlist timeout." );

    if ( !entry ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to remove a orig entry that didn't exist.");
        return;
    }

    timer_remove( &( entry->cleanup_timer ) );

    if ( !( entry->up ) ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to remove a orig entry that didn't have a mrt.");
        return;
    }

    prev = NULL;
    curr = entry->up->origlist;

    while( (curr) && (curr != entry) ) {
        prev = curr;
        curr = curr->next;
    }

    if ( prev == NULL ) {
        if ( curr == NULL ) {
            /* empty list */
            DEBUG( LOG_DEBUG, 0, "Tried to remove from empty list." );
            return;
        } else {
            /* first element remove */
            DEBUG( LOG_DEBUG, 0, "Removed first element" );
            entry->up->origlist = curr->next;
            free( entry );
        }
    } else {
        if ( curr == NULL ) {
            /* empty list */
            DEBUG( LOG_DEBUG, 0, "Tried to remove an unknown entry." );
            return;
        } else {
            /* somewhere in the middle or last element */
            DEBUG( LOG_DEBUG, 0, "Removed middle or last element.");
            prev->next = curr->next;
            free( entry );
        }
    }
}

/*
 *  mrt_table_set_leader( route, leader addr, leader hops )
 *  Sets the leader and hopcount of a particular route entry (a multicast group).
 */
void mrt_table_set_leader(mrt_table_t * mrt, u_int32_t grp_leader_addr, u_int8_t leader_hcnt)
{
    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to set the leader of a route that doesn't exist.");
        return;
    }

    mrt->grp_leader_addr = grp_leader_addr;
    mrt->leader_hcnt = leader_hcnt;
}

/*
 *  mrt_table_set_leader_hcnt( route, leader hops )
 *  Sets only the hopcount to the leader.  Leader remains the same.
 */
void mrt_table_set_leader_hopcount(mrt_table_t * mrt, u_int8_t leader_hcnt)
{
    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to set the hop count of a route that doesn't exist.");
        return;
    }
    
    mrt->leader_hcnt = leader_hcnt;
}

/*
 *  mrt_table_set_membership( route, boolean (memeber or not) )
 *  Sets our flag true if we're a member of the group to which this is a route.
 */
void mrt_table_set_membership(mrt_table_t * mrt, u_int8_t ismember)
{
    if ( !mrt ) {
        log(LOG_WARNING, 0, __FUNCTION__,
            "Attempting to set membership of a route that doesn't exist.");
        return;
    }

    mrt->is_member = ismember;
}

/*
 *  mrt_table_dump( )
 *  Prints the routing table  
 */
void mrt_table_dump(void)
{
    int index;
    mrt_table_t * curr;
    mrt_nexthop_t * hops;
    mrt_orig_t * origs;
    FILE *log;
    struct timeval now;
    struct tm *time;

    log=fopen("mrt_log", "a");
    if(log==NULL){
        perror("mrt_table_dump");
    }

    /* reset timer */
    timer_set_timeout( &( mrt_debug_timer ), 5000 );

    gettimeofday(&now, NULL);
    time = localtime(&now.tv_sec);

    fprintf( log, "\n# Entries: %d       Time: %02d:%02d:%02d.%03ld\n", mrt_tbl.num_entries,
          time->tm_hour, time->tm_min, time->tm_sec, now.tv_usec / 1000 );
    for ( index = 0; index < MRT_TABLESIZE; index++ )
    {
        curr = mrt_tbl.tbl[index];
        while ( curr ) {
            fprintf( log, "\t* * * * * * * * * * * * * * *\n" );
            fprintf( log, "\tGroup Addr[%s]\tLeader Addr[%s]\n",
                ip_to_str( curr->grp_addr ),
                ip_to_str( curr->grp_leader_addr ) );
            fprintf( log, "\tSeq #[%d]\tLeader Hop Count[%d]\n",
                curr->grp_seqno, curr->leader_hcnt );
            fprintf( log, "\tMembership[%d]", curr->is_member );
            fprintf( log, "\tRouter[%d]\tLeader[%d]\n", curr->is_router,
                curr->am_leader );
            fprintf( log, "\tBroken[%d]\tRepairing[%d]", curr->is_broken,
                curr->in_repair );
            fprintf( log, "\tGateway[%d]\n", curr->is_gateway );
            fprintf( log, "\tAct. Up. Count[%d]\tAct. Down. Count[%d]\n", 
                curr->activated_upstream_cnt,
                curr->activated_downstream_cnt );
            fprintf( log, "\tNext Hops\n" );
            hops = curr->nexthops;
            while( hops ) {
                fprintf( log, "\t\t* * * * * * * *\n\t\tAddr[%s]\n",
                    ip_to_str( hops->addr ) );
                fprintf( log, "\t\tActivated[%d]\tDirection[%c]\n",
                    hops->activated,
                    ( ( hops->direction == MRT_UPSTREAM ) ? ( 'U' ) : ( 'D' ) ) );
                fprintf( log, "\t\tGroup Hopcount[%d]\tGroup Seq#[%d]\n",
                    hops->grp_hcnt, hops->grp_seqno );
                hops = hops->next;
            }
		fprintf( log, "\tOriginator List\n" );
	    origs = curr->origlist;
	    while( origs ){
	      fprintf( log, "\t\t* * * * * * * *\n\t\tAddr[%s]\n",
		       ip_to_str( origs->orig_addr ) );
	      fprintf( log, "\t\tOrig Hopcount[%d]\tOrig Seq#[%d]\n",
		       origs->grp_hcnt, origs->grp_seqno );
	      origs = origs->next;
	    }
            curr = curr->next;
        }
    }

    fclose( log );
}

void mrt_table_become_leader(mrt_table_t *mrt){
    int i=0;
    /* CHECK: better way to select interface */
    /* set first enabled interface's ip as the group leader ip */
    for(i=0; i<MAX_NR_INTERFACES; i++){
        if(this_host.devs[i].enabled){
            mrt->grp_leader_addr=this_host.devs[i].ipaddr;
        }
    }
    if(!mrt->is_gateway) {
        mrt->is_member=1;
    }
    mrt->am_leader = 1; /* wahoo i'm now the REAL leader */
    mrt->grp_seqno=0;
    mrt->leader_hcnt=0;
    mrt->grph_update_cnt=1;

    /* mrt->is_router=1; */
    mrt_table_start_routing(mrt);
    
    /* send a hello */
    grph_send_callback(mrt);
}

mrt_nexthop_t *mrt_table_get_best_upstream(mrt_table_t *mrt){
    mrt_nexthop_t *nexthop, *besthop=NULL;

    nexthop=mrt->nexthops;

    while(nexthop!=NULL) {
        /* Before qualifying to be a upstream best hop, the next 
           hop must first, in fact be upstream.
           - if that is true, then the current nexthop will take the
           besthop position if either:
             - there is not current besthop
             - sequence number is greater than curr besthop
             - seq no are equal, but we have a lesser hop count
        */ 
        if(nexthop->direction == MRT_UPSTREAM &&
            (besthop == NULL ||
              (nexthop->grp_seqno > besthop->grp_seqno ||
                (nexthop->grp_seqno == besthop->grp_seqno &&
                  nexthop->grp_hcnt < besthop->grp_hcnt) ) ) ) {
            besthop=nexthop;
        }
        nexthop=nexthop->next;
    }

    return besthop;
}

mrt_nexthop_t *mrt_nexthop_find(mrt_table_t *mrt, uint32_t addr){
    mrt_nexthop_t *nexthop;

    nexthop=mrt->nexthops;

    while(nexthop!=NULL && nexthop->addr!=addr) {
        nexthop=nexthop->next;
    }

    return nexthop;
}

void mrt_table_start_routing(mrt_table_t *mrt){
    FILE *nh_log;
    
    mrt->is_router=1;

    nh_log=fopen("nh_log", "a");
    fprintf(nh_log, "JOIN: %x\n", mrt->grp_addr);
    fclose(nh_log);

    if(this_host.gateway_mode) {
        /* If in gateway mode, notify multicast router */
        mrt->is_gateway = 1;
        igmp_sendreply(mrt, dev_indices[0]);
    }
}

void mrt_table_stop_routing(mrt_table_t *mrt){
    FILE *nh_log;
    
    mrt->is_router=0;

    nh_log=fopen("nh_log", "a");
    fprintf(nh_log, "LEAVE: %x\n", mrt->grp_addr);
    fclose(nh_log);
}

#endif /* MAODV */
